﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using StyleCop;
using StyleCop.CSharp;

namespace Jsl.StyleCop.CSharp
{
    /// <summary>
    /// A helper utility for working with the StyleCop abstract syntax trees (AST).
    /// </summary>
    public static class AstHelper
    {
        #region Public Methods
        /// <summary>
        /// Enumerates the code unit's ancestors.
        /// </summary>
        /// <param name="codeUnit">The code unit.</param>
        /// <returns>The code unit's ancestors.</returns>
        public static IEnumerable<ICodeUnit> Ancestors(this ICodeUnit codeUnit)
        {
            Contract.Ensures(Contract.Result<IEnumerable<ICodeUnit>>() != null);

            var ancestor = (ICodeUnit)codeUnit.Parent;
            while (ancestor != null)
            {
                yield return ancestor;

                ancestor = (ICodeUnit)ancestor.Parent;
            }
        }

        /// <summary>
        /// Enumerates the code unit's ancestors include the given code part to the containing <see cref="CsElement"/>.
        /// </summary>
        /// <param name="codeUnit">The code unit.</param>
        /// <returns>The code unit's ancestors to the containing <see cref="CsElement"/>.</returns>
        public static IEnumerable<ICodeUnit> AncestorsIncludingThisToElement(this ICodeUnit codeUnit)
        {
            Contract.Ensures(Contract.Result<IEnumerable<ICodeUnit>>() != null);

            yield return codeUnit;

            foreach (var ancestor in codeUnit.AncestorsToElement())
            {
                yield return ancestor;
            }
        }

        /// <summary>
        /// Enumerates the code unit's ancestors to the containing <see cref="CsElement"/>.
        /// </summary>
        /// <param name="codeUnit">The code unit.</param>
        /// <returns>The code unit's ancestors to the containing <see cref="CsElement"/>.</returns>
        public static IEnumerable<ICodeUnit> AncestorsToElement(this ICodeUnit codeUnit)
        {
            Contract.Ensures(Contract.Result<IEnumerable<ICodeUnit>>() != null);

            foreach (var ancestor in codeUnit.Ancestors())
            {
                if (ancestor.CodePartType == CodePartType.Element)
                {
                    yield return ancestor.GetActualCsElement();
                    break;
                }
                else
                {
                    yield return ancestor;
                }
            }
        }

        /// <summary>
        /// Enumerates the children for the specified parent.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <returns>The children for the specified parent.</returns>
        public static IEnumerable<ICodeUnit> Children(this CsDocument parent)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }

            Contract.Ensures(Contract.Result<IEnumerable<ICodeUnit>>() != null);

            return new ICodeUnit[] { parent.RootElement }
                .Concat(parent.RootElement.Children());
        }

        /// <summary>
        /// Enumerates the children for the specified parent.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <returns>The children for the specified parent.</returns>
        public static IEnumerable<ICodeUnit> Children(this ICodeUnit parent)
        {
            Contract.Ensures(Contract.Result<IEnumerable<ICodeUnit>>() != null);

            var codeElement = parent as ICodeElement;
            if (codeElement != null)
            {
                foreach (var child in codeElement.ChildCodeElements.OfType<ICodeUnit>())
                {
                    yield return child;
                }
            }

            var codeUnit = parent as ICodeUnit;
            if (codeUnit != null)
            {
                foreach (var child in codeUnit.ChildExpressions)
                {
                    if (child != null)
                    {
                        yield return child;
                    }
                }

                foreach (var child in codeUnit.ChildStatements)
                {
                    if (child != null)
                    {
                        yield return child;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the closest common ancestor of the two code units.
        /// </summary>
        /// <param name="codeUnit1">The code unit1.</param>
        /// <param name="codeUnit2">The code unit2.</param>
        /// <returns>The closest common ancestor of the two code units.</returns>
        /// <remarks>
        /// This may return either of the two code units passed in if one is a child of the other.
        /// </remarks>
        public static ICodeUnit ClosestCommonAncestor(this ICodeUnit codeUnit1, ICodeUnit codeUnit2)
        {
            foreach (var ancestor1 in codeUnit1.AncestorsIncludingThisToElement())
            {
                foreach (var ancestor2 in codeUnit2.AncestorsIncludingThisToElement())
                {
                    if (object.ReferenceEquals(ancestor1, ancestor2))
                    {
                        return ancestor1;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the containing element.
        /// </summary>
        /// <param name="codeUnit">The code unit.</param>
        /// <returns>The containing element.</returns>
        public static CsElement ContainingElement(this ICodeUnit codeUnit)
        {
            return (CsElement)codeUnit.AncestorsToElement().Last();
        }

        /// <summary>
        /// Gets the statement, if any.
        /// </summary>
        /// <param name="codeUnit">The code unit.</param>
        /// <returns>The statement, if any.</returns>
        public static Statement ContainingStatement(this ICodeUnit codeUnit)
        {
            Contract.Requires(codeUnit != null);

            return codeUnit.Ancestors()
                .OfType<Statement>()
                .FirstOrDefault();
        }

        /// <summary>
        /// Enumerates the descendants for the specified parent.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <returns>The descendants for the specified parent.</returns>
        public static IEnumerable<ICodeUnit> Descendants(this CsDocument parent)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }

            return new ICodeUnit[] { parent.RootElement }
                .Concat(parent.RootElement.Descendants());
        }

        /// <summary>
        /// Enumerates the descendants for the specified parent.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <returns>The descendants for the specified parent.</returns>
        public static IEnumerable<ICodeUnit> Descendants(this ICodeUnit parent)
        {
            Contract.Ensures(Contract.Result<IEnumerable<ICodeUnit>>() != null);

            foreach (var child in parent.Children())
            {
                yield return child;

                foreach (var descendant in child.Descendants())
                {
                    yield return descendant;
                }
            }
        }

        /// <summary>
        /// Evaluates specified expression.
        /// </summary>
        /// <param name="expression">The expression to evaluate.</param>
        /// <param name="conditionEvaluators">The conditions to check for.</param>
        /// <returns><c>true</c> if the expression is <c>true</c>, <c>false</c> if the expression is <c>false</c>, <c>null</c> if it can't be determined.</returns>
        /// <remarks>
        /// This method is used to simplify boolean expressions. The conditionEvaluators are given chances to evaluate the
        /// whole or parts of the expression. They return if they know the expression is True, False or that they can't
        /// determine anything about the expression. This allows the conditionEvaluators to be very simple and this method
        /// will take care of AND and OR logic as well as negation.
        /// </remarks>
        public static bool? EvaluateExpression(
            this Expression expression,
            params Func<Expression, bool?>[] conditionEvaluators)
        {
            return expression.EvaluateExpression((IEnumerable<Func<Expression, bool?>>)conditionEvaluators);
        }

        /// <summary>
        /// Evaluates specified expression.
        /// </summary>
        /// <param name="expression">The expression to evaluate.</param>
        /// <param name="conditionEvaluators">The conditions to check for.</param>
        /// <returns><c>true</c> if the expression is <c>true</c>, <c>false</c> if the expression is <c>false</c>, <c>null</c> if it can't be determined.</returns>
        /// <remarks>
        /// This method is used to simplify boolean expressions. The conditionEvaluators are given chances to evaluate the
        /// whole or parts of the expression. They return if they know the expression is True, False or that they can't
        /// determine anything about the expression. This allows the conditionEvaluators to be very simple and this method
        /// will take care of AND and OR logic as well as negation.
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static bool? EvaluateExpression(
            this Expression expression,
            IEnumerable<Func<Expression, bool?>> conditionEvaluators)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }

            if (conditionEvaluators == null)
            {
                throw new ArgumentNullException("conditionEvaluators");
            }

            Contract.Requires(conditionEvaluators.Any());

            // See if any of the condition evaluators can handle the entire condition.
            foreach (var condition in conditionEvaluators)
            {
                var result = condition(expression);
                if (result != null)
                {
                    return result;
                }
            }

            // Handle true and false constants.
            var literalExpression = expression as LiteralExpression;
            if (literalExpression != null)
            {
                if (literalExpression.IsBooleanTrueConstant())
                {
                    return true;
                }
                else if (literalExpression.IsBooleanFalseConstant())
                {
                    return false;
                }
            }

            // If this is a parenthesized expression then return the evaluation of what is inside it.
            var parenthesizedExpression = expression as ParenthesizedExpression;
            if (parenthesizedExpression != null)
            {
                return parenthesizedExpression.InnerExpression.EvaluateExpression(conditionEvaluators);
            }

            // Handle unary expressions.
            var unaryExpression = expression as UnaryExpression;
            if (unaryExpression != null)
            {
                return EvaluateUnaryExpression(unaryExpression, conditionEvaluators);
            }

            // Handle and/or condition.
            var conditionalLogicalExpression = expression as ConditionalLogicalExpression;
            if (conditionalLogicalExpression != null)
            {
                return EvaluateConditionalLogicalExpession(conditionalLogicalExpression, conditionEvaluators);
            }

            // Handle relational expressions.
            var relationalExpression = expression as RelationalExpression;
            if (relationalExpression != null)
            {
                return EvaluateRelationalExpession(relationalExpression, conditionEvaluators);
            }

            // Nothing matched so we have no idea about this condition.
            return null;
        }

        /// <summary>
        /// Evaluates the not null check expression.
        /// </summary>
        /// <param name="expression">The expression to check.</param>
        /// <param name="conditionEvaluator">The condition evaluator.</param>
        /// <returns><c>false</c> if this is a null check, <c>true</c> if it is a not null check.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static bool? EvaluateNotNullCheckExpression(
            this Expression expression,
            Func<Expression, bool?> conditionEvaluator)
        {
            return !expression.EvaluateNullCheckExpression(conditionEvaluator);
        }

        /// <summary>
        /// Evaluates the null check expression.
        /// </summary>
        /// <param name="expression">The expression to check.</param>
        /// <param name="conditionEvaluator">The condition evaluator.</param>
        /// <returns><c>true</c> if this is a null check, <c>false</c> if it is a not null check.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        public static bool? EvaluateNullCheckExpression(
            this Expression expression,
            Func<Expression, bool?> conditionEvaluator)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }

            if (conditionEvaluator == null)
            {
                throw new ArgumentNullException("conditionEvaluator");
            }

            Contract.EndContractBlock();

            RelationalExpression.Operator @operator;
            Expression leftHandSide;
            Expression rightHandSide;
            if (IsRelationalExpression(expression, out @operator, out leftHandSide, out rightHandSide))
            {
                // The only two operators that make since are == and !=.
                if ((@operator != RelationalExpression.Operator.EqualTo)
                    && (@operator != RelationalExpression.Operator.NotEqualTo))
                {
                    return null;
                }

                // Make sure one side is null and get the checked expression from the other side.
                Expression checkedExpression = null;
                if (leftHandSide.IsNullLiteral())
                {
                    if (!rightHandSide.IsNullLiteral())
                    {
                        checkedExpression = rightHandSide;
                    }
                }
                else if (rightHandSide.IsNullLiteral())
                {
                    checkedExpression = leftHandSide;
                }

                // If one side was null...
                if (checkedExpression != null)
                {
                    var result = conditionEvaluator(checkedExpression);
                    if (@operator == RelationalExpression.Operator.EqualTo)
                    {
                        return result;
                    }
                    else
                    {
                        return !result;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Finds the first statement in the <see cref="StatementBlock"/>, if any.
        /// </summary>
        /// <param name="statement">The statement to check.</param>
        /// <returns>The specified statement if it is not a <see cref="BlockStatement"/>, otherwise the first statement.</returns>
        public static Statement FirstStatementInBlock(this Statement statement)
        {
            // If this isn't a BlockStatement then just return the passed in statement.
            var blockStatement = statement as BlockStatement;
            if (blockStatement == null)
            {
                return statement;
            }

            // If there are no statements in the BlockStatement, return null.
            if (blockStatement.ChildStatements.Count == 0)
            {
                return null;
            }

            // There may be many BlockStatements inside BlockStatements so recursively find the first one.
            return blockStatement.ChildStatements.First().FirstStatementInBlock();
        }

        /// <summary>
        /// Gets the statements, if any, that follow the given statement.
        /// </summary>
        /// <param name="statement">The statement.</param>
        /// <returns>The statements that follow the given statement.</returns>
        public static IEnumerable<Statement> FollowingStatements(this Statement statement)
        {
            return statement.StatementsAtSameLevel()
                .SkipWhile(siblingStatement => !object.ReferenceEquals(statement, siblingStatement))
                .Skip(1);
        }

        /// <summary>
        /// Gets the argument modifiers.
        /// </summary>
        /// <param name="methodInvocation">The method invocation.</param>
        /// <param name="literalExpression">The literal expression.</param>
        /// <returns>The argument modifiers if found. <see cref="ParameterModifiers.None"/> if not found.</returns>
        public static ParameterModifiers GetArgumentModifiers(
            this MethodInvocationExpression methodInvocation,
            LiteralExpression literalExpression)
        {
            if (methodInvocation == null)
            {
                throw new ArgumentNullException("methodInvocation");
            }

            if (literalExpression == null)
            {
                throw new ArgumentNullException("literalExpression");
            }

            var matchingArgument =
                (from argument in methodInvocation.Arguments
                 where object.ReferenceEquals(argument.Expression, literalExpression)
                 select argument)
                .FirstOrDefault();

            return (matchingArgument != null) ? matchingArgument.Modifiers : ParameterModifiers.None;
        }

        /// <summary>
        /// Gets the parameter from variable.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="variable">The variable.</param>
        /// <returns>The parameter if found, <c>null</c> otherwise.</returns>
        public static Parameter GetParameterFromVariable(this CsElement element, Variable variable)
        {
            var parameterContainer = element as IParameterContainer;
            if (parameterContainer != null)
            {
                Contract.Assume(parameterContainer.Parameters != null);

                return parameterContainer.Parameters
                    .Where(param => param.Name == variable.Name)
                    .FirstOrDefault();
            }

            return null;
        }

        /// <summary>
        /// Gets the <see cref="Variable"/> from the <see cref="VariableDeclaratorExpression"/>.
        /// </summary>
        /// <param name="variableDeclarator">The variable declarator.</param>
        /// <returns>The <see cref="Variable"/>.</returns>
        public static Variable GetVariable(this VariableDeclaratorExpression variableDeclarator)
        {
            if (variableDeclarator == null)
            {
                throw new ArgumentNullException("variableDeclarator");
            }

            Contract.EndContractBlock();

            if (variableDeclarator.ContainingStatement() is CatchStatement)
            {
                return variableDeclarator.ContainingStatement().Variables[variableDeclarator.Identifier.Text];
            }

            var parentCodeUnit = (ICodeUnit)variableDeclarator.ContainingStatement().Parent;
            return parentCodeUnit.Variables[variableDeclarator.Identifier.Text];
        }

        /// <summary>
        /// Gets the variable usage.
        /// </summary>
        /// <param name="literalExpression">The literal expression.</param>
        /// <returns>The variable usage.</returns>
        /// <remarks>
        /// The exception variable declared in a catch statement is counted as a declaration and assignment.
        /// </remarks>
        public static VariableUsageInformation GetVariableUsage(this LiteralExpression literalExpression)
        {
            if (literalExpression == null)
            {
                throw new ArgumentNullException("literalExpression");
            }

            Contract.EndContractBlock();

            // See if this is actually a variable.
            Variable variable;
            ICodeUnit hoistedInside;
            if (!literalExpression.TryGetVariable(out variable, out hoistedInside))
            {
                return null;
            }

            // The catch statements exception variable is counted as a declaration and assignment.
            var catchStatement = literalExpression.ContainingStatement() as CatchStatement;
            if (catchStatement != null)
            {
                return new VariableUsageInformation(literalExpression, variable, VariableUsage.DeclarationAndAssignment, hoistedInside);
            }

            // See if it is the variable being declared.
            var variableDeclarator = literalExpression.Parent as VariableDeclaratorExpression;
            if (variableDeclarator != null)
            {
                if (object.ReferenceEquals(variableDeclarator.Identifier, literalExpression))
                {
                    if (variableDeclarator.Initializer == null)
                    {
                        return new VariableUsageInformation(literalExpression, variable, VariableUsage.Declaration, hoistedInside);
                    }
                    else
                    {
                        return new VariableUsageInformation(literalExpression, variable, VariableUsage.DeclarationAndAssignment, hoistedInside);
                    }
                }
            }

            // The variable name might be the same as a field name so weed out the member accesses.
            var memberAccess = literalExpression.Parent as MemberAccessExpression;
            if (memberAccess != null)
            {
                if (object.ReferenceEquals(memberAccess.LeftHandSide, literalExpression))
                {
                    return new VariableUsageInformation(literalExpression, variable, VariableUsage.Access, hoistedInside);
                }
                else
                {
                    return null;
                }
            }

            // See if it is on the left side of an assignment statement.
            var assignment = literalExpression.Parent as AssignmentExpression;
            if (assignment != null)
            {
                if (object.ReferenceEquals(assignment.LeftHandSide, literalExpression))
                {
                    if (assignment.IsAssignmentWithSelfReference())
                    {
                        return new VariableUsageInformation(literalExpression, variable, VariableUsage.AccessAndAssignment, hoistedInside);
                    }
                    else
                    {
                        return new VariableUsageInformation(literalExpression, variable, VariableUsage.Assignment, hoistedInside);
                    }
                }
            }

            // See if it is a ref or out parameter.
            var methodInvocation = literalExpression.Parent as MethodInvocationExpression;
            if (methodInvocation != null)
            {
                var parameterModifiers = methodInvocation.GetArgumentModifiers(literalExpression);
                if ((parameterModifiers & ParameterModifiers.Out) != 0)
                {
                    return new VariableUsageInformation(literalExpression, variable, VariableUsage.OutParameter, hoistedInside);
                }
                else if ((parameterModifiers & ParameterModifiers.Ref) != 0)
                {
                    return new VariableUsageInformation(literalExpression, variable, VariableUsage.RefParameter, hoistedInside);
                }
            }

            // If its not an assignment, ref parameter or out parameter then the only other possibility is access.
            return new VariableUsageInformation(literalExpression, variable, VariableUsage.Access, hoistedInside);
        }

        /// <summary>
        /// Formats the location for display in errors, etc.
        /// </summary>
        /// <param name="location">The location.</param>
        /// <returns>The location formatted for reading.</returns>
        public static string HumanReadable(this CodeLocation location)
        {
            if (location == null)
            {
                throw new ArgumentNullException("location");
            }

            return
                string.Format(
                    CultureInfo.CurrentCulture,
                    "({0}, {1})",
                    location.StartPoint.LineNumber,
                    location.StartPoint.IndexOnLine);
        }

        /// <summary>
        /// Determines if the two code units are in the same element.
        /// </summary>
        /// <param name="item">The item to check.</param>
        /// <param name="target">The target to search for.</param>
        /// <returns><c>true</c> if the two code units are in the same element.</returns>
        public static bool InSameElement(this ICodeUnit item, ICodeUnit target)
        {
            return object.ReferenceEquals(item.ParentElement(), target.ParentElement());
        }

        /// <summary>
        /// Determines whether the <see cref="AssignmentExpression"/> is like +=, -=, *=, etc.
        /// </summary>
        /// <param name="assignment">The assignment.</param>
        /// <returns><c>true</c> if the <see cref="AssignmentExpression"/> is like +=, -=, *=, etc.</returns>
        public static bool IsAssignmentWithSelfReference(this AssignmentExpression assignment)
        {
            if (assignment == null)
            {
                throw new ArgumentNullException("assignment");
            }

            switch (assignment.OperatorType)
            {
                case AssignmentExpression.Operator.AndEquals:
                case AssignmentExpression.Operator.DivisionEquals:
                case AssignmentExpression.Operator.LeftShiftEquals:
                case AssignmentExpression.Operator.MinusEquals:
                case AssignmentExpression.Operator.ModEquals:
                case AssignmentExpression.Operator.MultiplicationEquals:
                case AssignmentExpression.Operator.OrEquals:
                case AssignmentExpression.Operator.PlusEquals:
                case AssignmentExpression.Operator.RightShiftEquals:
                case AssignmentExpression.Operator.XorEquals:
                    return true;

                case AssignmentExpression.Operator.Equals:
                    return false;

                default:
                    throw new InvalidOperationException("Unknown assignment operator type.");
            }
        }

        /// <summary>
        /// Determines whether the expression is a Boolean constant.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <returns><c>true</c> if the expression is a Boolean constant.</returns>
        public static bool IsBooleanConstant(this Expression expression)
        {
            return expression.IsBooleanFalseConstant() || expression.IsBooleanTrueConstant();
        }

        /// <summary>
        /// Determines whether the expression is the <c>false</c> boolean constant.
        /// </summary>
        /// <param name="expression">The literal expression.</param>
        /// <returns><c>true</c> if is the <c>false</c> boolean constant.</returns>
        public static bool IsBooleanFalseConstant(this Expression expression)
        {
            return expression.IsLiteral("false");
        }

        /// <summary>
        /// Determines whether the expression is the <c>true</c> boolean constant.
        /// </summary>
        /// <param name="expression">The literal expression.</param>
        /// <returns><c>true</c> if is the <c>true</c> boolean constant.</returns>
        public static bool IsBooleanTrueConstant(this Expression expression)
        {
            return expression.IsLiteral("true");
        }

        /// <summary>
        /// Determines whether if the item is a descendant of the specified ancestor.
        /// </summary>
        /// <param name="codeUnit">The code unit.</param>
        /// <param name="ancestor">The ancestor.</param>
        /// <returns><c>true</c> if the item is descendant of the specified ancestor.</returns>
        public static bool IsDescendantOf(this ICodeUnit codeUnit, ICodeUnit ancestor)
        {
            return codeUnit.Ancestors().Any(item => object.ReferenceEquals(item, ancestor));
        }

        /// <summary>
        /// Determines whether the expression is a literal with the specified name.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <param name="literalText">The literal text.</param>
        /// <returns><c>true</c> if the expression is a literal with the specified name.</returns>
        public static bool IsLiteral(this Expression expression, string literalText)
        {
            var literal = expression as LiteralExpression;
            return (literal != null)
                && (literal.Text == literalText);
        }

        /// <summary>
        /// Determines whether expression is the "base" literal.
        /// </summary>
        /// <param name="expression">The expression to check.</param>
        /// <returns><c>true</c> if this expression is the "base" literal.</returns>
        public static bool IsLiteralBase(this Expression expression)
        {
            return expression.IsLiteral("base");
        }

        /// <summary>
        /// Determines whether expression is the "this" literal.
        /// </summary>
        /// <param name="expression">The expression to check.</param>
        /// <returns><c>true</c> if this expression is the "this" literal.</returns>
        public static bool IsLiteralThis(this Expression expression)
        {
            return expression.IsLiteral("this");
        }

        /// <summary>
        /// Determines whether this is a reference to the specified member.
        /// </summary>
        /// <param name="expression">The expression to check.</param>
        /// <param name="memberName">Name of the member.</param>
        /// <returns><c>true</c> if this is a reference to the specified member.</returns>
        public static bool IsMemberReference(this Expression expression, string memberName)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }

            // See if the expression is this.X or base.X.
            var memberAccess = expression as MemberAccessExpression;
            var isQualifiedMemberAccess = (memberAccess != null)
                && (memberAccess.LeftHandSide.IsLiteralThis() || memberAccess.LeftHandSide.IsLiteralBase());

            // If it wasn't a qualified member access then we have to make sure it isn't a variable with the same name as the member.
            Expression expressionToCheck;
            if (isQualifiedMemberAccess)
            {
                expressionToCheck = memberAccess.RightHandSide;
            }
            else
            {
                var matchingVariable =
                    from variable in expression.Variables
                    where variable.Name == memberName
                    select variable;
                if (matchingVariable.Any())
                {
                    return false;
                }

                expressionToCheck = expression;
            }

            var literal = expressionToCheck as LiteralExpression;
            return (literal != null)
                && (literal.Text == memberName);
        }

        /// <summary>
        /// Determines whether the expression is a <c>null</c> check.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <param name="expectedOperator">The expected operator.</param>
        /// <param name="checkedExpression">The checked expression.</param>
        /// <returns><c>true</c> if the expression is a <c>null</c> check.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Need to return two pieces of information.")]
        public static bool IsNullCheck(
            this Expression expression,
            RelationalExpression.Operator expectedOperator,
            out Expression checkedExpression)
        {
            RelationalExpression.Operator @operator;
            Expression leftHandSide;
            Expression rightHandSide;
            if (expression.IsRelationalExpression(out @operator, out leftHandSide, out rightHandSide))
            {
                if (@operator == expectedOperator)
                {
                    if (leftHandSide.IsNullLiteral())
                    {
                        if (!rightHandSide.IsNullLiteral())
                        {
                            checkedExpression = rightHandSide;
                            return true;
                        }
                    }
                    else if (rightHandSide.IsNullLiteral())
                    {
                        checkedExpression = leftHandSide;
                        return true;
                    }
                }
            }

            // Return failure.
            checkedExpression = null;
            return false;
        }

        /// <summary>
        /// Determines whether the expression is a null literal.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <returns><c>true</c> if the expression is a null literal.</returns>
        public static bool IsNullLiteral(this Expression expression)
        {
            var literalExpression = expression as LiteralExpression;
            return (literalExpression != null)
                && (literalExpression.Text == "null");
        }

        /// <summary>
        /// Determines whether literal expression is an out or ref parameter.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="variable">The variable to check.</param>
        /// <returns><c>true</c> if literal expression is an out or ref parameter.</returns>
        public static bool IsOutOrRefParameter(this CsElement element, Variable variable)
        {
            var parameter = element.GetParameterFromVariable(variable);
            if (parameter != null)
            {
                return (parameter.Modifiers & (ParameterModifiers.Out | ParameterModifiers.Ref)) != ParameterModifiers.None;
            }

            return false;
        }

        /// <summary>
        /// Determines whether the literal expression is being used as an out parameter.
        /// </summary>
        /// <param name="methodInvocation">The method invocation.</param>
        /// <param name="literalExpression">The literal expression.</param>
        /// <returns><c>true</c> if the literal expression is being used as an out parameter.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We are specifically only looking at method invocations.")]
        public static bool IsOutParameter(this MethodInvocationExpression methodInvocation, LiteralExpression literalExpression)
        {
            return (methodInvocation.GetArgumentModifiers(literalExpression) & ParameterModifiers.Out) != 0;
        }

        /// <summary>
        /// Determines whether the specified element has the readonly keyword.
        /// </summary>
        /// <param name="element">The element to check.</param>
        /// <returns><c>true</c> if the specified element has the readonly keyword.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only look at elements.")]
        public static bool IsReadOnly(this CsElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            return element.Tokens.Any(token => token.CsTokenType == CsTokenType.Readonly);
        }

        /// <summary>
        /// Determines whether the literal expression is being used as a ref parameter.
        /// </summary>
        /// <param name="methodInvocation">The method invocation.</param>
        /// <param name="literalExpression">The literal expression.</param>
        /// <returns><c>true</c> if the literal expression is being used as a ref parameter.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "The parameter types are as needed.")]
        public static bool IsRefParameter(this MethodInvocationExpression methodInvocation, LiteralExpression literalExpression)
        {
            return (methodInvocation.GetArgumentModifiers(literalExpression) & ParameterModifiers.Ref) != 0;
        }

        /// <summary>
        /// Determines whether the expression is a relational expression.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <param name="operator">The operator in the relational expression.</param>
        /// <param name="leftHandExpression">The left hand expression.</param>
        /// <param name="rightHandExpression">The right hand expression.</param>
        /// <returns><c>true</c> if the expression is a relational expression.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification = "I need to return four pieces of information.")]
        public static bool IsRelationalExpression(
            this Expression expression,
            out RelationalExpression.Operator @operator,
            out Expression leftHandExpression,
            out Expression rightHandExpression)
        {
            // Remove the outer ParenthesizedExpression if there is one.
            RelationalExpression relationalExpression;
            var parenthesizedExpression = expression as ParenthesizedExpression;
            if (parenthesizedExpression == null)
            {
                relationalExpression = expression as RelationalExpression;
            }
            else
            {
                relationalExpression = parenthesizedExpression.InnerExpression as RelationalExpression;
            }

            if (relationalExpression != null)
            {
                @operator = relationalExpression.OperatorType;
                leftHandExpression = relationalExpression.LeftHandSide;
                rightHandExpression = relationalExpression.RightHandSide;
                return true;
            }

            // Didn't find anything so return failure.
            @operator = RelationalExpression.Operator.EqualTo;
            leftHandExpression = null;
            rightHandExpression = null;
            return false;
        }

        /// <summary>
        /// Determines whether the specified element is static.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns><c>true</c> if the specified element is static.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only look at elements.")]
        public static bool IsStatic(this CsElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            return element.Tokens.Any(token => token.CsTokenType == CsTokenType.Static);
        }

        /// <summary>
        /// Determines if the parent element is a class or struct.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns><c>true</c> if the parent element is a class or struct.</returns>
        public static bool IsType(this CsElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            var elementType = element.ElementType;
            return elementType == ElementType.Class || elementType == ElementType.Struct;
        }

        /// <summary>
        /// Determines whether the specified literal expression is a variable.
        /// </summary>
        /// <param name="literalExpression">The literal expression.</param>
        /// <returns><c>true</c> if the specified literal expression is a variable.</returns>
        public static bool IsVariable(this LiteralExpression literalExpression)
        {
            Variable variable;
            return literalExpression.TryGetVariable(out variable);
        }

        /// <summary>
        /// Determines if the <see cref="LiteralExpression"/> is a variable being set.
        /// </summary>
        /// <param name="literalExpression">The literal expression that may be a variable.</param>
        /// <returns><c>true</c> if the literal expression is a variable being set.</returns>
        /// <remarks>
        /// A variable can be set in an assignment, when used as an out parameter and in its declaration with an initializer.
        /// </remarks>
        public static bool IsVariableSetting(this LiteralExpression literalExpression)
        {
            var variableUsage = literalExpression.GetVariableUsage();
            if (variableUsage != null)
            {
                return variableUsage.Usage.IsVariableSetting();
            }

            return false;
        }

        /// <summary>
        /// Determines whether the usage is setting the variable.
        /// </summary>
        /// <param name="usage">The usage.</param>
        /// <returns><c>true</c> if the usage is a variable being set.</returns>
        public static bool IsVariableSetting(this VariableUsage usage)
        {
            switch (usage)
            {
                case VariableUsage.Access:
                case VariableUsage.Declaration:
                    return false;

                case VariableUsage.AccessAndAssignment:
                case VariableUsage.Assignment:
                case VariableUsage.DeclarationAndAssignment:
                case VariableUsage.OutParameter:
                case VariableUsage.RefParameter:
                    return true;

                default:
                    throw new InvalidOperationException(
                        string.Format(CultureInfo.CurrentUICulture, "Unknown variable usage at line {0}.", usage));
            }
        }

        /// <summary>
        /// Gets the parent element of this item.
        /// </summary>
        /// <param name="codeUnit">The code unit.</param>
        /// <returns>The parent element of this item.</returns>
        /// <remarks>
        /// The <see cref="ICodeUnit.ParentElement"/> throws exceptions when called from inside a method, and possibly
        /// other places. The issue is that the <see cref="ICodeUnit.Parent"/> isn't a <see cref="Method"/> but a
        /// <see cref="CodeUnit"/> which isn't derived from <see cref="CsElement"/>.
        /// </remarks>
        public static CsElement ParentElement(this ICodeUnit codeUnit)
        {
            return (CsElement)codeUnit.AncestorsToElement().Last();
        }

        /// <summary>
        /// Gets the parent if statement if the specified statement is in one.
        /// </summary>
        /// <param name="statement">The statement.</param>
        /// <returns>The parent if statement if the specified statement is in one.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only want to look at statements.")]
        public static IfStatement ParentIf(this Statement statement)
        {
            if (statement == null)
            {
                throw new ArgumentNullException("statement");
            }

            if (statement.Parent is BlockStatement)
            {
                return statement.Parent.Parent as IfStatement;
            }
            else
            {
                return statement.Parent as IfStatement;
            }
        }

        /// <summary>
        /// Gets the statements, if any, that are before the given statement.
        /// </summary>
        /// <param name="statement">The statement.</param>
        /// <returns>The statements that before the given statement.</returns>
        public static IEnumerable<Statement> PreviousStatements(this Statement statement)
        {
            return statement.StatementsAtSameLevel()
                .TakeWhile(siblingStatement => !object.ReferenceEquals(statement, siblingStatement));
        }

        /// <summary>
        /// Removes the <c>this</c> accessor, if any.
        /// </summary>
        /// <param name="expression">The expression to check.</param>
        /// <returns>The original expression or the expression without the <c>this</c>.</returns>
        public static Expression RemoveThisAccessor(this Expression expression)
        {
            var memberAccess = expression as MemberAccessExpression;
            if (memberAccess != null)
            {
                if (memberAccess.LeftHandSide.IsLiteralThis())
                {
                    return memberAccess.RightHandSide;
                }
            }

            return expression;
        }

        /// <summary>
        /// Enumerates the siblings statements for the given statement.
        /// </summary>
        /// <param name="statement">The statement.</param>
        /// <returns>The siblings statements for the given statement.</returns>
        public static IEnumerable<Statement> SiblingStatements(this Statement statement)
        {
            return statement.StatementsAtSameLevel().Except(new Statement[] { statement });
        }

        /// <summary>
        /// Enumerates the statements at the same level as the given statement.
        /// </summary>
        /// <param name="statement">The statement.</param>
        /// <returns>The statements for at the same level as the given statement.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We are specifically looking at statements.")]
        public static IEnumerable<Statement> StatementsAtSameLevel(this Statement statement)
        {
            if (statement == null)
            {
                throw new ArgumentNullException("statement");
            }

            var parentCodeUnit = (ICodeUnit)statement.Parent;
            return parentCodeUnit.ChildStatements;
        }

        /// <summary>
        /// Evaluates the <see cref="IfStatement"/> and determines the <see cref="Statement"/> that will be executed based
        /// on the <paramref name="conditionEvaluators"/>.
        /// </summary>
        /// <param name="ifStatement">The <see cref="IfStatement"/> to evaluate.</param>
        /// <param name="statement">The first statement based on the IF conditions.</param>
        /// <param name="conditionEvaluators">The condition evaluators.</param>
        /// <returns>The <see cref="Statement"/>, if it can be determined, that will be executed based on the <paramref name="conditionEvaluators"/>.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Need to return two pieces of information.")]
        public static bool TryEvaluateIf(
            this IfStatement ifStatement,
            out Statement statement,
            params Func<Expression, bool?>[] conditionEvaluators)
        {
            return ifStatement.TryEvaluateIf(out statement, (IEnumerable<Func<Expression, bool?>>)conditionEvaluators);
        }

        /// <summary>
        /// Evaluates the <see cref="IfStatement"/> and determines the <see cref="Statement"/> that will be executed based
        /// on the <paramref name="conditionEvaluators"/>.
        /// </summary>
        /// <param name="ifStatement">The <see cref="IfStatement"/> to evaluate.</param>
        /// <param name="statement">The first statement based on the IF conditions.</param>
        /// <param name="conditionEvaluators">The condition evaluators.</param>
        /// <returns>The <see cref="Statement"/>, if it can be determined, that will be executed based on the <paramref name="conditionEvaluators"/>.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design.")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Need to return two pieces of information.")]
        public static bool TryEvaluateIf(
            this IfStatement ifStatement,
            out Statement statement,
            IEnumerable<Func<Expression, bool?>> conditionEvaluators)
        {
            if (ifStatement == null)
            {
                throw new ArgumentNullException("ifStatement");
            }

            var conditionResult = ifStatement.ConditionExpression.EvaluateExpression(conditionEvaluators);

            // If we couldn't determine anything then return failure.
            if (conditionResult == null)
            {
                statement = null;
                return false;
            }

            // If the expression was TRUE then return the true part of the IF.
            if (conditionResult.Value)
            {
                return ifStatement.EmbeddedStatement.TryEvaluateInnerIf(
                    out statement,
                    conditionEvaluators);
            }

            // If the expression was FALSE then return the false part of the IF.
            if (ifStatement.AttachedElseStatement == null)
            {
                statement = null;
                return true;
            }
            else
            {
                return ifStatement.AttachedElseStatement.EmbeddedStatement.TryEvaluateInnerIf(
                    out statement,
                    conditionEvaluators);
            }
        }

        /// <summary>
        /// Tries to get a string constant.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <param name="value">The string constant value, if any.</param>
        /// <returns><c>true</c> if a string constant was found.</returns>
        public static bool TryGetStringConstant(this Expression expression, out string value)
        {
            // The argument should be a literal.
            var literalExpression = expression as LiteralExpression;
            if (literalExpression == null)
            {
                value = null;
                return false;
            }

            // The value should be a hard-coded string.
            var literalText = literalExpression.Text;
            if (!literalText.StartsWith("\"", StringComparison.Ordinal)
                || !literalText.EndsWith("\"", StringComparison.Ordinal))
            {
                value = null;
                return false;
            }

            // Remove the quotes from around the text.
            value = literalText.Substring(1, literalText.Length - 2);
            return true;
        }

        /// <summary>
        /// Tries to get variable from the <see cref="LiteralExpression"/>.
        /// </summary>
        /// <param name="literalExpression">The literal expression.</param>
        /// <param name="variable">The variable.</param>
        /// <returns><c>true</c> if the <see cref="LiteralExpression"/> is a variable usage.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "All the other variable related methods take a LiteralExpression.")]
        public static bool TryGetVariable(this LiteralExpression literalExpression, out Variable variable)
        {
            ICodeUnit hoistedInside;
            return literalExpression.TryGetVariable(out variable, out hoistedInside);
        }

        /// <summary>
        /// Tries to get variable from the <see cref="LiteralExpression"/>.
        /// </summary>
        /// <param name="literalExpression">The literal expression.</param>
        /// <param name="variable">The variable.</param>
        /// <param name="hoistedInside">The code unit the variable is hoisted inside.</param>
        /// <returns><c>true</c> if the <see cref="LiteralExpression"/> is a variable usage.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "This follows the TryGetValue pattern.")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "All the other variable related methods take a LiteralExpression.")]
        public static bool TryGetVariable(this LiteralExpression literalExpression, out Variable variable, out ICodeUnit hoistedInside)
        {
            if (literalExpression == null)
            {
                throw new ArgumentNullException("literalExpression");
            }

            hoistedInside = null;

            // Walk up the ancestor tree until we find where/if we are declared.
            foreach (var ancestor in literalExpression.AncestorsToElement())
            {
                // Return the variable if it was found.
                variable = ancestor.Variables[literalExpression.Text];
                if (variable != null)
                {
                    return true;
                }

                // If the variable usage was inside a lambda expression of an anonymous method then the variable was hoisted
                // since we haven't got to the level it is declared at.
                if (hoistedInside == null)
                {
                    if ((ancestor is LambdaExpression)
                        || (ancestor is AnonymousMethodExpression))
                    {
                        hoistedInside = ancestor;
                    }
                }
            }

            variable = null;
            return false;
        }
        #endregion Public Methods

        #region Private Methods
        private static bool? EvaluateConditionalLogicalExpession(
            ConditionalLogicalExpression conditionalLogicalExpression,
            IEnumerable<Func<Expression, bool?>> conditionEvaluators)
        {
            var leftResult = conditionalLogicalExpression.LeftHandSide.EvaluateExpression(conditionEvaluators);
            var rightResult = conditionalLogicalExpression.RightHandSide.EvaluateExpression(conditionEvaluators);

            if (conditionalLogicalExpression.OperatorType == ConditionalLogicalExpression.Operator.Or)
            {
                return leftResult | rightResult;
            }
            else
            {
                return leftResult & rightResult;
            }
        }

        private static bool? EvaluateRelationalExpession(
            RelationalExpression relationalExpression,
            IEnumerable<Func<Expression, bool?>> conditionEvaluators)
        {
            // We can only handle boolean evaluations so only == or !=.
            if ((relationalExpression.OperatorType != RelationalExpression.Operator.EqualTo)
                && (relationalExpression.OperatorType != RelationalExpression.Operator.NotEqualTo))
            {
                return null;
            }

            var leftResult = relationalExpression.LeftHandSide.EvaluateExpression(conditionEvaluators);
            var rightResult = relationalExpression.RightHandSide.EvaluateExpression(conditionEvaluators);

            if ((leftResult == null) || (rightResult == null))
            {
                return null;
            }

            if (relationalExpression.OperatorType == RelationalExpression.Operator.EqualTo)
            {
                return leftResult == rightResult;
            }
            else
            {
                return leftResult != rightResult;
            }
        }

        private static bool? EvaluateUnaryExpression(
            UnaryExpression unaryExpression,
            IEnumerable<Func<Expression, bool?>> conditionEvaluators)
        {
            switch (unaryExpression.OperatorType)
            {
                case UnaryExpression.Operator.Not:
                    return !unaryExpression.Value.EvaluateExpression(conditionEvaluators);

                // Not handling other types of unary operators because they are more for math.
                default:
                    return null;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "CsElement", Justification = "Name of the type.")]
        private static CsElement GetActualCsElement(this ICodePart codePart)
        {
            if (codePart.CodePartType != CodePartType.Element)
            {
                throw new ArgumentException("Expected an ICodeUnit with CodeUnitType of Element.");
            }

            // If this is actually a CsElement then just return it.
            var element = codePart as CsElement;
            if (element != null)
            {
                return element;
            }

            var elementParent = codePart.Parent as CsElement;
            if (elementParent == null)
            {
                throw new InvalidOperationException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        "The parent of a CsElement should be a CsElement at position {0}.",
                        codePart.Location));
            }

            // The child element of the elementParent that has the same CodeLocation should be the element we are looking for.
            Contract.Assume(elementParent.ChildElements != null);
            return
                (from siblingElement in elementParent.ChildElements
                 where siblingElement.Location == codePart.Location
                 select siblingElement).Single();
        }

        private static bool TryEvaluateInnerIf(
            this Statement partStatement,
            out Statement statement,
            IEnumerable<Func<Expression, bool?>> conditionEvaluators)
        {
            var firstStatement = partStatement.FirstStatementInBlock();
            var innerIfStatement = firstStatement as IfStatement;
            if (innerIfStatement != null)
            {
                Statement innerStatement;
                if (innerIfStatement.TryEvaluateIf(out innerStatement, conditionEvaluators))
                {
                    // We could evaluate the IF so return the statement from that IF.
                    statement = innerStatement;
                    return true;
                }
            }

            statement = firstStatement;
            return true;
        }
        #endregion Private Methods
    }
}